package titt.io;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

import titt.config.ValueConfig;

import titt.io.encoding.CompositeDecoder;
import titt.io.encoding.CompositeEncoder;
import titt.io.encoding.Decoder;
import titt.io.encoding.Encoder;
import titt.io.encoding.EncodingFacade;
import titt.io.encoding.ObjectDecoder;
import titt.io.encoding.ObjectEncoder;
import titt.io.encoding.array.ArrayDecoder;
import titt.io.encoding.array.ArrayEncoder;
import titt.io.encoding.stringable.AsStringDecoder;
import titt.io.encoding.stringable.AsStringEncoder;
import titt.io.encoding.stringable.StringDecoder;
import titt.io.encoding.stringable.StringEncoder;
import titt.io.encoding.stringable.StringFromBytesDecoder;
import titt.io.encoding.stringable.StringFromBytesEncoder;
import titt.io.encoding.stringable.VoidDecoder;
import titt.io.encoding.stringable.VoidEncoder;

import titt.io.reader.head.HeadReader;
import titt.io.reader.head.HeadReaderImpl;

import titt.io.socket.TITTSocket;
import titt.io.socket.TITTSocketImpl;
import titt.io.socket.in.TITTInputStream;
import titt.io.socket.in.TITTInputStreamImpl;
import titt.io.socket.out.TITTOutputStream;
import titt.io.socket.out.TITTOutputStreamImpl;

import titt.io.writer.head.HeadWriter;
import titt.io.writer.head.HeadWriterImpl;

import titt.service.content.listener.AsyncListener;
import titt.service.content.listener.CombinedListener;
import titt.service.content.listener.ForgivingListener;
import titt.service.content.listener.IOTimeoutListener;
import titt.type.Type;
import titt.util.io.pipe.PipeListener;
import titt.util.io.pipe.StreamPiper;
import titt.util.io.pipe.StreamPiperImpl;

public class IOFactory {
	
	private static EncodingFacade ef = null;
	
	public static TITTInputStream createInputStream(InputStream in) {
		return new TITTInputStreamImpl(in);
	}
		
	public static TITTOutputStream createOutputStream(OutputStream out) {
		return new TITTOutputStreamImpl(out);
	}
	
	/*
	 * TITTSocket
	 */
	
	public static TITTSocket createTITTSocket(Socket s) throws Exception {
		return new TITTSocketImpl(s);
	}

	public static TITTSocket createTITTSocket() throws Exception {
		return new TITTSocketImpl(new Socket(ValueConfig.get().getHost(), ValueConfig.get().getPort()));
	}
	
	/*
	 * Pipes
	 */
	
	public static StreamPiper createStreamPiper() {
		return new StreamPiperImpl();
	}
	
	/*
	 * Encoders / Decoders here
	 */
	
	public static Encoder createEncoder() {
		return new CompositeEncoder(new ArrayEncoder(new AsStringEncoder()), new ObjectEncoder(), new VoidEncoder());
	}
	
	public static Decoder createDecoder() {
		return new CompositeDecoder(new ArrayDecoder(new AsStringDecoder()), new ObjectDecoder(), new VoidDecoder());
	}
	
	public static StringEncoder createStringEncoder() {
		return new StringFromBytesEncoder(new CompositeEncoder(new ArrayEncoder(new AsStringEncoder()), new VoidEncoder()));
	}
	
	public static StringDecoder createStringDecoder() {
		return new StringFromBytesDecoder(new CompositeDecoder(new ArrayDecoder(new AsStringDecoder()), new VoidDecoder()));
	}
	
	/*
	 * HeaderWriters / HeaderReaders here
	 */
	
	public static HeadWriter createHeadWriter(TITTOutputStream out) {
		return new HeadWriterImpl(out);
	}
	
	public static HeadReader createHeadReader(TITTInputStream in) {
		return new HeadReaderImpl(in);
	}

	/*
	 * Facades here
	 */
	
	public static EncodingFacade createEncodingFacade() {
		if(ef == null)
			ef = createEncodingFacade(IOFactory.createEncoder(), IOFactory.createDecoder(), IOFactory.createStringEncoder(), IOFactory.createStringDecoder());
		return ef;
	}
	
	private static EncodingFacade createEncodingFacade(final Encoder e, final Decoder d, final StringEncoder se, final StringDecoder sd) {
		return new EncodingFacade() {
			@Override
			public byte[] encode(Type t, Object o) {
				return e.encode(t, o);
			}
			@Override
			public boolean isEncodeable(Type t) {
				return e.isEncodeable(t);
			}
			@Override
			public Object decode(Type t, byte[] b) {
				return d.decode(t, b);
			}
			@Override
			public boolean isDecodeable(Type t) {
				return d.isDecodeable(t);
			}
			@Override
			public String encodeToString(Type t, Object o) {
				return se.encodeToString(t, o);
			}
			@Override
			public boolean isStringEncodeable(Type t) {
				return se.isStringEncodeable(t);
			}
			@Override
			public Object decodeFromString(Type t, String enc) {
				return sd.decodeFromString(t, enc);
			}
			@Override
			public boolean isStringDecodeable(Type t) {
				return sd.isStringDecodeable(t);
			}
		};
	}
	
	/*
	 * Listeners here
	 */
	
	public static PipeListener createListener(PipeListener... listeners) {
		if(listeners.length == 0)
			return null;
		if(listeners.length == 1)
			return listeners[0];
		
		return new CombinedListener(listeners);
	}
	
	private static PipeListener createAsyncListener(PipeListener listener) {
		if(listener == null)
			return null;
		return new AsyncListener(1500, new ForgivingListener(listener));
	}
	
	public static PipeListener createInternalListener(PipeListener... listeners) {
		return IOFactory.createAsyncListener(IOFactory.createListener(listeners));
	}
	
	public static PipeListener createInternalNuderListener(TITTOutputStream out, TITTInputStream in, PipeListener... listeners) {
		PipeListener l = IOFactory.createInternalListener(listeners);
		
		if(l == null) 
			return null;

		return new IOTimeoutListener(in, out, l);
	}
	
}

